package org.jage.student.missevo.solution.agent;

import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;

import org.jage.action.ComplexAction;
import org.jage.action.SingleAction;
import org.jage.action.context.AddAgentActionContext;
import org.jage.address.AgentAddress;
import org.jage.address.IAgentAddress;
import org.jage.address.selector.AnyUnusedSelector;
import org.jage.address.selector.IAddressSelector;
import org.jage.address.selector.agent.AgentAddressSelector;
import org.jage.agent.AgentException;
import org.jage.agent.IAgent;
import org.jage.agent.SimpleAgent;
import org.jage.platform.component.annotation.Inject;
import org.jage.platform.component.exception.ComponentException;
import org.jage.property.InvalidPropertyOperationException;
import org.jage.property.InvalidPropertyPathException;
import org.jage.property.PropertyField;
import org.jage.query.AgentEnvironmentQuery;
import org.jage.solution.ISolution;
import org.jage.student.missevo.solution.NullSolution;
import org.jage.student.missevo.solution.death.IShouldDie;
import org.jage.student.missevo.solution.energy.ISaveEnergyActionContext;
import org.jage.student.missevo.solution.energy.IShouldExchangeEnergy;
import org.jage.student.missevo.solution.energy.StandardSaveEnergyActionContext;
import org.jage.student.missevo.solution.migration.IMigration;
import org.jage.student.missevo.solution.migration.IShouldMigrate;
import org.jage.student.missevo.solution.reproduction.IEnergyPassing;
import org.jage.student.missevo.solution.reproduction.IReproduction;
import org.jage.student.missevo.solution.reproduction.IShouldReproduce;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


public class MissEvoSimpleAgent extends SimpleAgent {

	private static final long serialVersionUID = 1L;

	private final Logger log = LoggerFactory.getLogger(MissEvoSimpleAgent.class);

	@Inject
	@PropertyField(propertyName = "kind")
	private int kind;
	
	@Inject
	@PropertyField(propertyName = "energy")
	private double energy;
	
	@Inject
	@PropertyField(propertyName = "die")
	private IShouldDie dieStrategy;
	
	@PropertyField(propertyName = "shouldReproduce")
	@Inject
	private IShouldReproduce shouldReproduceStrategy;

	@PropertyField(propertyName = "reproduce")
	@Inject
	private IReproduction reproduceStrategy;

	@PropertyField(propertyName = "energyPass")
	@Inject
	private IEnergyPassing energyPassStrategy;
	
	@PropertyField(propertyName = "shouldMigrate")
	@Inject
	private IShouldMigrate shouldMigrateStrategy;

	@PropertyField(propertyName = "migrate")
	@Inject
	private IMigration migrateStrategy;

	
	@PropertyField(propertyName = "shouldExchangeEnergy")
	@Inject
	private IShouldExchangeEnergy shouldExchangeEnergyStrategy;
	
	@PropertyField(propertyName = "solution")
	@Inject
	private ISolution solution;

	private long step;
	
	@Inject
	public MissEvoSimpleAgent() {
		log.info("Constructing MiSS EVO Simple Agent");
	}

	@Override
	public void init() throws ComponentException {
		super.init();
		log.info("Initializing MiSS EVO Simple Agent: {}", getAddress());
		
		if (dieStrategy == null) throw new IllegalArgumentException("dieStrategy not set");
		if (shouldReproduceStrategy == null) throw new IllegalArgumentException("shouldReproduceStrategy not set");
		if (reproduceStrategy == null) throw new IllegalArgumentException("reproduceStrategy not set");
		if (energyPassStrategy == null) throw new IllegalArgumentException("energyPassStrategy not set");
		if (shouldExchangeEnergyStrategy == null) throw new IllegalArgumentException("shouldExchangeEnergyStrategy not set");
		if (shouldMigrateStrategy == null) throw new IllegalArgumentException("shouldMigrateStrategy not set");
		
		solution = new NullSolution();
	}
	
	@Override
	public boolean finish() {
		log.info("Finishing MiSS EVO  Simple Agent: {}", getAddress());
		return true;
	}

	
	@Override
	public void step() {
		step++;
		
		log.info("Agent {} step {} energy {}", new Object[] { getAddress(), getStep(), getEnergy() });
		
		//considerLooking();
		
		try {
			if (dieStrategy.decide(this)) {	
				doDie();
				return;
			}
			
			// energy exchange
			if (shouldExchangeEnergyStrategy.decide(getAgentEnvironment(),getProperties())) {
				ISaveEnergyActionContext context = new StandardSaveEnergyActionContext();
				IAddressSelector<IAgentAddress> anyAgent = new AnyUnusedSelector<IAgentAddress>();

				Collection<MissEvoSimpleAgent> agents = queryEnvironment(new AgentEnvironmentQuery<MissEvoSimpleAgent, MissEvoSimpleAgent>());
				Collection<IAgentAddress> allAddresses = new HashSet<IAgentAddress>();
				for (MissEvoSimpleAgent a : agents) {
					if (a.isAlive()) {
						allAddresses.add(a.getAddress());
					}
				}

				if (allAddresses.size() > 1) {
					anyAgent.initialize(allAddresses, Collections.singleton(getAddress()));
					ComplexAction exchangeAction = new ComplexAction();
					exchangeAction.addChild(new SingleAction(new AgentAddressSelector(getAddress()), context, "saveEnergy"));
					exchangeAction.addChild(new SingleAction(anyAgent, context,	"compareEnergy"));
					exchangeAction.addChild(new SingleAction(new AgentAddressSelector(getAddress()), context,"modifyEnergy"));
					doAction(exchangeAction);
				}		
			}
			
			// reproduction
			if (shouldReproduceStrategy.decide(getAgentEnvironment(),getProperties())) {
				ISolution childsSolution = reproduceStrategy.reproduce(null, getProperties());
				double childsEnergy = energyPassStrategy.computeEnergyToPass(null, getProperties());

				SingleAction action = createAddNewAgentAction();
				IAgent newAgent = ((AddAgentActionContext) action.getContext()).getAgent();

				try {
					newAgent.getProperties().getProperty("kind").setValue(getKind());
					newAgent.getProperties().getProperty("energy").setValue(childsEnergy);
					newAgent.getProperties().getProperty("solution").setValue(childsSolution);
					spendEnergy(childsEnergy);
					doAction(action);
				} catch (InvalidPropertyOperationException e) {
					log.error(e.getMessage());
				}
			}
			
			
			//migration
			AgentEnvironmentQuery<? extends IAgent, ?> query = null;
			query = shouldMigrateStrategy.query();
			Collection<?> queryResult = null;
			if (query != null) {
				// check for other islands
				queryResult = queryParentEnvironment(query);
				if (queryResult.size() > 1) {
					if (shouldMigrateStrategy.decide(queryResult, this)) {
						query = migrateStrategy.query();
						if (query != null) {
	
							// a workaround to remove this agent's island from
							// QueryResult:
							IAgent island = null; 
							for (Iterator<IAgent> iter = (Iterator<IAgent>)queryResult.iterator(); iter.hasNext();) {
								island = iter.next();
								if (island.getProperty("address").getValue().equals(getParentAddress())) {
									break;
								}
							}
							queryResult.remove(island);
							
							// migrate
							AgentAddress destination = migrateStrategy.migrate(queryResult, this);
							doMove(destination);
							log.info("Agent: {} migrating from {} to environment: {}",
									new Object[]{ getAddress(), getParentAddress(), destination });
						}
					}
				}
			}
			
			}
			catch (AgentException ae) {
				log.error("Error in step", ae);
			} catch (InvalidPropertyPathException ae) {
				// TODO Auto-generated catch block
				log.error("Error in step", ae);
			}
		
		
		}
			
			
		

	
		
		//spendEnergy(10.0);
		
//		try {
//			Thread.sleep(200);
//		} catch (InterruptedException e) {
//			log.error("Interrupted", e);
//		}
		
		// notify potential monitors
		//notifyMonitorsForChangedProperties();
	

	
	
	public int getKind() {
		return kind;
	}
	
	public long getAge() {
		return step;
	}
	
	public boolean isAlive() {
		return !dieStrategy.decide(this);
	}
	
	public long getStep() {
		return step;
	}
	
    public double getEnergy() {
		return energy;
	}
    
    public ISolution getSolution() {
		return solution;
	}

    public void setEnergy(double energy) {
		this.energy = energy;
	}
    
    public void setSolution(ISolution solution) {
		this.solution = solution;
	}

    private void spendEnergy(double amount) {
    	energy -= amount;
    }
    
	/**
	 * consider looking	
	 * @throws AgentException 
	 * @throws InvalidPropertyPathException 
	 */
	private void considerLooking() {
		Collection<MissEvoSimpleAgent> answer;
		try {
			AgentEnvironmentQuery<MissEvoSimpleAgent, MissEvoSimpleAgent> query = 
				new AgentEnvironmentQuery<MissEvoSimpleAgent, MissEvoSimpleAgent>();

			answer = queryEnvironment(query);
			
			
			log.info("Agent: {} can see in its environment: {} following agents:", getAddress(), getParentAddress());
			for (MissEvoSimpleAgent entry : answer) {
				IAgentAddress agentAddress = (IAgentAddress)entry.getProperty("address").getValue();
				if (agentAddress != getAddress()) {
					log.info("    agent: {}:", agentAddress);
				}
			}
			
			spendEnergy(13.0);
			
		} catch (AgentException e) {
			log.error("Agent exception while looking", e);
		} catch (InvalidPropertyPathException e) {
			log.error("Invalid property while looking", e);
		}
	}
	
}
